Program #2 - Counting Presses of a Button
;-----------------------------------------------------------------------;
; SWCNT.ASM Counts presses of a pushbutton on RB7 ;
;-----------------------------------------------------------------------;
LIST P=16F84 ; tells which processor is used
INCLUDE "p16f84.inc" ; defines various registers etc. Look it over.
ERRORLEVEL -224 ; suppress annoying message because of tris
__CONFIG _PWRTE_ON & _LP_OSC & _WDT_OFF ; configuration switches
;-----------------------------------------------------------------------;
; Here we set up our own registers start at the 1st free address ;
;-----------------------------------------------------------------------;
CBLOCK H'0C'
dlycount ; counter used in delays
ENDC
ORG 0 ; start a program memory location zero
;-----------------------------------------------------------------------;
; First we set up all bits of PORT A and B as outputs ;
;-----------------------------------------------------------------------;
movlw B'00000000' ; all bits low in W
tris PORTA ; contents of W copied to PORT A ...
movlw B'00010000' ; RB4 input, all other output
tris PORTB ; and PORT B
movlw B'00000000' ; port B pull-ups active
option
;-----------------------------------------------------------------------;
; This is the main program ;
;-----------------------------------------------------------------------;
clrf PORTB ; start with zero
loop:
btfsc PORTB, 4 ; switch closed, (gives 0)?
goto loop ; not yet
; switch has been detected closed
incf PORTB, f ; add 1 to port B
; wait a while to make sure switch has
; settled (debounce closure)
movlw D'10' ; wait about 10 msec
call nmsec
; now wait for release
btfss PORTB, 4 ; will be high (1) when released
goto $ -1 ; still low
; now must wait a make sure bouncing stopped
movlw D'10' ; 10 milliseconds
call nmsec
; and check again
btfss PORTB, 4 ; if set, still released
goto $ -5 ; still low start release wait all over
goto loop ; loop forever
;-----------------------------------------------------------------------;
; Here is a subroutine: delays number of millisec in 'W' on entry ;
;-----------------------------------------------------------------------;
nmsec:
movwf dlycount ; save 'W' in a register of our own
dlyloop:
nop ; each nop is 0.122 milliseconds
nop
nop ; each total loop is 8 X 0.122 = 0.976 msec
nop
nop
decfsz dlycount, f ; this is 0.122 msec if skip not taken
goto dlyloop ; this is 2 X 0.122 msec
return ; back to calling point
end ; end of program
There are four items that are set when you "burn" the program
into the PIC that are entirely separate from the program itself. These are set
by supplying a configuration word which is burned into the PIC. The bits making
up this word are formed in the line that starts...'__CONFIG'.
- Power up timer To prevent the program from starting
too soon after the power is switched on, (everything may not be settled yet),
you can activate a power up timer with '_PWRTE_ON'
- Type of Oscillator The oscillator you use for the
PIC is either an RC oscillator, a crystal oscillator, (XT), a high speed
crystal,(HS), or a low speed low power, (LP) crystal. Your circuit may not
respond properly if you have the wrong oscillator type selected.
- Watchdog Timer To prevent your program from running
off into 'never-never land' without the ability to recover, a watchdog timer,
(WDT), can be activated. It will restart the program every 18 milliseconds or
so unless a CLRWDT, (clear watchdog timer), command is executed during this
period. Normally you don't want it activated,(_WDT_OFF). It can easily cause
your program to seem to act strangely.
- Code Protection To prevent someone from copying the
code you put into the PIC, you can 'code protect' PIC. Normally you don't want
to do this and '_CP_OFF' is the default so you can leave it out.
In this program we use a subroutine instead of having
everything in line. Subroutines are useful for code that has to be called many
times. It saves space because only one copy in needed in the code. The routine
is entered using a 'call' instruction and the 'return' instruction returns to
the instruction immediately after the call instruction.
Time delays can be handled by using the time taken to execute
instructions rather than waiting for a flag set by at timer overflow. An
advantage of the PIC instruction set is that almost all instructions take the
same amount of time to execute. This is the period of the oscillator divided by
four. For a 4 mHz crystal this is 1 microsecond. For a 32.768 kHz crystal it is
about 122 microseconds. The only exceptions to this are when the next
instruction is not the next in sequence, (goto,call,return,btfss etc.). In these
cases, the program counter has to be changed and the instruction takes two
instruction cycles rather than one. Notice the delay subroutine at the end of
the program which delays for the number of milliseconds given in 'W' on entry.
To keep from having external resistors to pull up pins set to
inputs, you can use the internal weak-pull-ups available on port B only. To
activate them, bit 7 of OPTION, (RBPU), must be set to 0. This affects all the
bits of port B but only when they are set up as inputs. They are essentially
like a high resistance to Vdd.
Note that Port B now has an input, (RB4), with a pushbutton
switch to ground. This could be as simple as a loose wire attached to GND. You
touch this to a wire attached to the RB4 pad.
I no longer recommend attaching loose wires to the test
circuit. The problem is that if these are made outputs they can move around and
short against something else. I recently smoked a power supply and burnt out
some ports on a PIC. I think the problem was loose wires. For the same reason I
also recommend mounting the test circuit board on stand-offs or at least
covering the bottom with electrical tape. It is easy to lay the board down on
something that will short things out.
Much of the code is taken up in 'de-bouncing' the button. When
a pushbutton is pressed or released it is not usually a simple on-off contact.
The contact usually bounces on and off a number of times before settling down.
How long this takes depends on the construction of the switch, but 10
milliseconds is usually long enough unless the switch is real poor.
My approach is to look only for the first contact. Usually the
action determined by closing the switch takes over 10 msec to happen so further
checking for closure is not required. If it is, a 10 msec delay can be built in.
This is done in the program above since the increment takes hardly any time at
all. This does not allow for a false trigger or noise spike, that would require
an additional check after the 10 msec delay.
You must be concerned with bounce on release also. We cannot
loop back and check for closure again as soon as we detect the first open. We
must be certain that no bouncing remains on release. In the program, a second
check is made after 10 msec and if the switch still appears closed, additional
10 msec check/s are made. A faster way would require checking for a certain
number of consecutive highs at regular intervals.
We have created our own register 'dlycount'. One way to do is
in MPASM is to use a block of names between 'CBLOCK' and 'ENDC'. The number
after 'CBLOCK' indicates where in the register memory these names are to
start.
BINCNT.ASM used only what Microchip calls special purpose
registers, those built into the chip and given definite names in 'p16F84.inc'.
There are 12 of these at register locations 0-11 and that is why our register
was defined at location 12, ( H'0C' ). There are 68 eight bit register locations
in total. Actually, there are two 'pages' of 68 bit registers, but most are
duplicated in both pages, including all of the general purpose registers that we
get to make up names for. It is just those few that are different in the two
pages that screw everything up. You don't want to know about this 'page
flipping' until you have to.
'nop' stands for no operation. It doesn't do anything really
but it does take up time and that is what we use it for here. The time taken is
one instruction cycle, (the crystal period divided by four), 0.122 msec in this
case.
A branching instruction we haven't seen before is 'decfsz'. In
words it's: 'decrement the given register and skip the next instruction if the
result of the decrement gives zero'. It's very handy for counting down to zero
from a given number.
'goto $ -1' is pretty straight forward but 'goto $ -5'
requires a little counting and larger jumps get worse. What could you use to
replace the 'goto $ -5'? Try it.
Try commenting out parts or all of the debounce code and see
what happens. You could also try changing those 10 msec values and try different
switches. Some of the cheaper pushbuttons are really bad. You will find that the
simple loose wire 'switch' is also very bad.
Do you have an old rotary dial phone around? If you take the
cover off and find the wire coming from the rotary dial to the same terminal as
the red wire, you can disconnect it. Use this wire from the dial and the other
one from the dial, (to terminal with green wire), in place of the pushbutton. As
you rotate the dial clockwise, these two wires will short. As the dial returns
counter-clockwise it will open and close giving pulses at a rate of 10 per
second. Modify swcnt.asm to reset to zero at the first short and then count the
pulses.
We really need higher count values. Can you think of a way to
do this? How about adding a 1 second delay routine, (make it a subroutine). You
could then write a program that showed the high 4 bits of PORTB for a second or
two and then the low 4 bits. Of course you couldn't count additional switch
closures while this was going on, ( can anyone say 'interrupt'?).
How would you go about getting only the low 4 bits of Port B?
Do you know about AND? You could AND PORTB with H'0F' but that destroys the top
part of PORTB. You really need a copy of PORTB that you can destroy, or actually
maybe you shouldn't be incrementing PORTB anyway. You are going to need to
destroy PORTB to display the top 4 bits of the count. Maybe you should really be
incrementing a register that you create. You can then take parts of this
register and transfer them to PORTB for display. Quite a bit to think about.
Have fun!